/*
  The main file for executing the shell.

  Ben Marks, Caleb Smith; January 2014
  Operating Systems, CS 45
*/

// Included Files
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h> 
#include<sys/wait.h>
#include<assert.h>
#include<errno.h>
#include<readline/readline.h> 

#include"parsecmd.h"
#include"history.h"


// Function Declarations
int doCD( char ** argv );
int execCommand( int bg, char ** argv );
void execCommands( char *** argvArr, int numCommands, int * bgs );
void sigchld(int sig);
void doPipe( char ** argv1, char ** argv2, int bg );
char * getPrompt(char * home, char * hostname, int hostLen);
int getFullPaths( int numCommands, char *** argvArr );
int execChild( char ** argv, int readPipe, int writePipe, 
	       int numPipes, int * fds );
extern char * get_current_dir_name();


//#define DEBUG

/******************************************************************/
int main(void){ 

  char *cmd_line, *cmd_line_extra;
  char ** argv;
  int i;
  char hostname[256];
  int hostLen;
  history h;

  // Initialize history
  memset(&h, 0, sizeof(history));

  // Install signal handler for SIGCHLD
  signal(SIGCHLD, sigchld);

  /* Get the hostname so that we can display a Unix style prompt with
     the appropriate name. */
  if ( gethostname( hostname, 255 ) ) {
    perror("gethostname");
    fprintf(stderr, "something might be funny with your command prompt\n");
    fprintf(stderr, "gethostname failed.");
  }
  hostLen = strlen( hostname );
  /* Get the home directory so that we can display in Unix style prompt
     with ~ if we are in the home directory */
  char * home = getenv( "HOME" );
  
  // Main loop for interacting with user
  for(;;) {

    // Construct appropriate prompt based on cwd
    char * prompt = getPrompt( home, hostname, hostLen );
    cmd_line = readline( prompt );
    free( prompt );

    if(! cmd_line ) {
      // Error getting input from user
      exit(0);
    }
    else if(!*cmd_line) {
      // User typed an empty line
      free( cmd_line );
      continue;
    }

    /* Transparently handle the history processing. */
    switch(is_history_command(cmd_line)){
      case 2:
	// User typed history
        add_to_history( &h, cmd_line );
        display_history(&h);
        free(cmd_line);
        continue;
      case 1:
	// User wants to reexecute a commmand
        cmd_line_extra = cmd_line;
        cmd_line = get_history(&h, cmd_line);
        free(cmd_line_extra);
        if(!cmd_line){
          fprintf(stderr, "History item not found\n");
          continue;
        }
	// If the command they want to reexecute is the history command ...
        if ( ! strcmp( cmd_line, "history" ) ) {
	  add_to_history(&h, cmd_line);
          display_history(&h);
          free(cmd_line);
          continue;
        }
    }

    // Command unrelated to history - add it to circular buffer
    add_to_history(&h, cmd_line);

    // Handle some weird syntax, like no spaces between |, &, < 
    cmd_line = preParser( cmd_line );


    // Tokenize into argv array
    // argv is an array of dynamically allocated pointers to tokens
    argv = split( cmd_line, " " );
   
    /* Check for various bad input forms */
    if ( areErrors( argv ) ) {
      fprintf(stderr, "Error: Invalid Command.\n");
      free( cmd_line );
      free( argv );
      continue;
    }



    // Check for exit builtin command
    if (! strcmp( "exit", argv[0] ) ) {
      free( cmd_line );
      free( argv );
      free_history(&h); /* clean history struct */
      exit(0);
    }

    // Check for cd builtin command
    if ( doCD( argv ) ) {
#ifdef DEBUG
      char * ret = get_current_dir_name();
      printf("\nCWD: %s \n", ret );
      free( ret );
#endif
      free( argv );
      free( cmd_line );
      continue;
    }
    
 #ifdef DEBUG
    int idx = 0;
    while ( argv[idx] ) {
      printf("\nArgv[%d]  %s\n", idx, argv[idx] );
      idx ++;
    }
#endif

    // Check for empty command
    if ( ! argv ) {
      continue;
    }
    
    // Check for piped command
    int numCommands = 0;
    /* argvArr is a dynamically allocated array of argvs, one for each 
       piped command */
    char *** argvArr = handlePipes( argv, &numCommands );

#ifdef DEBUG
    printf("Executing %d commands.", numCommands);
#endif
    
    // Make sure argv[0] is a full path for all commands
    if ( getFullPaths( numCommands, argvArr ) ) {
      free( argvArr );
      free( argv );
      free(cmd_line);
      continue;
    }

    // Check and see if the commands should be in the foreground or 
    // background
    int bgs[ numCommands ];
    for ( i = 0; i < numCommands; i ++ ) {
      bgs[i] = isBG( argvArr[i] );
    }


#ifdef DEBUG
    for ( i = 0; i < numCommands; i ++ ) {
      printf("Command Number: %d\n", i);
      idx = 0;
      while ( argvArr[i][idx] ) {
	printf("\t%d: %s\n", idx, argvArr[i][idx] );
	idx ++;
      }
      printf("\tCommand in %s\n", ( bgs[i] ? "Background" : "Foreground" ) );
    }
#endif

    
    if(numCommands > 1){
      // If we have a piped command, execute as such
      execCommands( argvArr, numCommands, bgs );
    } else {
      // Otherwise, can just use simpler function
      execCommand( bgs[0], argvArr[0] );
    }
    
    // Free all of the full paths that we generated
    for ( i = 0; i < numCommands; i ++ ) {
      free( argvArr[i][0] );
    }
    free( argvArr ); // Array of argvs for piped commands
    free(argv);      // Array of pointers into main command line
    free(cmd_line);  // Main command line (dynamically allocated by readline())
  
  } /* And, execute the next command */


  return 0;
}

/****************************************************/
/*                   HELPER FUNCTIONS               */
/****************************************************/

/*
  getFullPaths: Takes in argv arrays and ensures that argv[0] is a full path 
  to an executable. All argv[0] buckets are dynamically allocated to hold the
  full path string. For instance, if argv[0] was "ls", after this function it
  would be "/bin/ls"

  Parameters: 
     numCommands - number of argv's in the array of argv's
     argvArr - array of argv's, where we want to change each argv[0]

  Returns:
     0 on success
     1 on failure (if no full path found)
 */
int getFullPaths(int numCommands, char *** argvArr) {

  int i, j;
  int err = 0;
  for ( i = 0; i < numCommands; i ++ ) {
    if ( ! isFullPath( argvArr[i][0] ) ) {
      char * full = getPath( getenv("PATH"), argvArr[i][0] ) ;
      if ( !full ) {
	fprintf(stderr, "Invalid Command: %s\n", argvArr[i][0]);
	err = 1;
	break;
      }
      else { 
	argvArr[i][0] = full;
      }
    }
    else {
      argvArr[i][0] = strdup( argvArr[i][0] );
    }
  }
  if ( err ) {
    for ( j = 0; j < i; j ++ ) {
      free( argvArr[j][0] );
    }
    return 1;
  }
  return 0;
}

/*
  getPrompt: Construct the command line prompt to include the current
  hostname and working directory.

  Parameters: 
    home - string with user's home directory path
    hostname - string with current hostname
    hostLen - length of hostname string

  Returns:
    A dynamically allocated string containing a prompt of the form
    <HOSTNAME> [ < CWD > ]$ 

 */
char * getPrompt( char * home, char * hostname, int hostLen ) {

  int i;
  char * prompt;

  char * cwd = get_current_dir_name();
  if ( strcmp( home, cwd ) ) {  
    /* If we're not in the home directory, get the current 
       dir name. */
    char ** cwdSplit = split( cwd, "/" );
    i = 0;
    while ( cwdSplit[i] ) { i++; }
    int dirLen = strlen( cwdSplit[i-1] );
    int promptLen = hostLen + 6 + dirLen + 1;
    prompt = Malloc( promptLen * sizeof(char) );
    snprintf(prompt, promptLen, "**%s[%s]$ ", hostname, cwdSplit[i-1] );  
    free( cwdSplit );
  }
  else {
    /* In home directory, we should just show ~ as the dir name */
    int promptLen = hostLen + 6 + 1 + 1;
    prompt = Malloc( promptLen * sizeof(char) );
    snprintf( prompt, promptLen, "**%s[~]$ ", hostname ); 
  }
  free( cwd );

  return prompt;

}

/*
  execChild: The main function for executing a single command.

  Parameters:
    char ** argv - Argv array for the child; argv[0] is a full path
                   to the executable.
    int writePipe - File descriptor for write end of pipe child should
                    use, or 0 otherwise.
    int readPipe - File descriptor for the read end of the pipe child should
                   use, or 0 otherwise.
    int numPipes - Number of pipes child has open and should close.
    int * fds - Array of file descriptors pointing to pipes that child has open

  Returns: PID of child, or -1 on error with fork()
 */
pid_t execChild( char ** argv, int writePipe, int readPipe,
		 int numPipes, int * fds ) {

  int j;
  pid_t toRet = fork();
  if ( toRet < 0 ) {
    perror("fork");
    return -1;
  }
  if ( toRet > 0 ) {
    // Parent
    // Close any pipes that the child may have inhereted
    if ( readPipe ) { close( readPipe ); }
    if ( writePipe) { close( writePipe); }

    return toRet;
  }
  // Deal with the pipes that matter to me
  if ( writePipe ) {
#ifdef DEBUG
    printf("Writing to pipe %d\n", writePipe );
#endif
    dup2( writePipe, fileno( stdout ) );
  }
  if ( readPipe ) {
#ifdef DEBUG
    printf("Reading from pope %d\n", readPipe);
#endif
    dup2( readPipe, fileno( stdin ) ) ;
  }
  // And close the rest of the pipes
  for ( j = 0; j < 2 * numPipes; j ++ ) {
    close( fds[ j ] );
  }
  
  // Handle I/O Redirection
  int in, out, err;
  if ( ioRedirect( argv, &in, &out, &err ) ) {
    fprintf(stderr, "Invalid syntax.\n");
    free ( fds );
    exit(1);
  }
  if ( in  ) { dup2( in , fileno( stdin  ) ); }
  if ( out ) { dup2( out, fileno( stdout ) ); }
  if ( err ) { dup2( err, fileno( stderr ) ); }
  
  if ( execv( argv[0], argv ) ) {
    perror("execv");
    exit(1);
  }
  // Never get here !
  return 0; // Keep gcc happy
}

/*
  execCommands - execute multiple commands joined by a pipe.

  Parameters:
    char *** argvArr - array of argv's, one for each command
    int numCommands - number of commands we are executing
    int * bgs - background / foreground status for each command

  Returns - nothing.

 */
void execCommands( char *** argvArr, int numCommands, int * bgs ) {

  int i;
  int pids[ numCommands ];
  int numPipes = numCommands - 1;

  /* Make pipes to use */
  int * fds = Malloc( numPipes * 2 * sizeof(int) );
  for ( i = 0; i < numPipes; i ++ ) {
    if ( pipe( &fds[ 2*i] ) ) {
      perror("pipe");
      free( fds );
      return;
    }
  }

#ifdef DEBUG
  for ( i = 0; i < numPipes; i ++ ) {
    printf("Pipe No %d: Read(%d)  Write(%d)\n", i, fds[2*i], fds[2*i + 1] );
  }
#endif
  
  /* First Child: Redirect output to pipe */
  pids[0] = execChild( argvArr[0], 
		       fds[1], // Write end of pipe
		       0, // No read end
		       numPipes, fds ) ;
  
  if ( pids[0] < 0 ) {
    free ( fds );
    return;
  }
  
  /* Children 1 to N-1 */
  for ( i = 1; i < numCommands - 1; i ++ ) {
    pids[i] = execChild( argvArr[i], 
			 fds[2*i + 1], // Write end of pipe
			 fds[2*(i-1)],  // Read end of pipe
			 numPipes, fds );
    if ( pids[i] < 0 ) {
      free ( fds );
      return;
    }
    
  }
   
  /* Last Child */
  pids[numCommands - 1] = 
    execChild( argvArr[ numCommands - 1], 
	       0,  // No write end of pipe
	       fds [2*(numPipes - 1 )],  // Read end of pipe
	       numPipes, fds );

  if ( pids[numCommands - 1] < 0 ) {
    perror("fork");
    free ( fds );
    return;
  }
  
  /* Parent optionally waits for the last process to finish */
  if ( !bgs[ numCommands - 1 ] ) {
    int status;
    for ( i = 0; i < numCommands; i ++ ) {
      if ( waitpid( pids[i], & status, 0 ) < 0 ) {
	if ( errno != ECHILD ) {
	  perror("pipe waitpid");
	}
      }
    }
  }
  
  free( fds );
  
  return;
}


/*
  Helper function to handle the builtin cd command. Executes a
  cd if it is present.

  Inputs: char ** argv - argv of command currently being executed.

  Returns: 0 - command is not cd
           1 - command is cd
*/
int doCD( char ** argv ) {

  if ( ! strcmp( argv[0], "cd" ) ) {
    if ( argv[1] ) {
      if ( chdir( argv[1] ) ) {
	perror("chdir failed: ");
      }
    }
    else {
      if ( chdir( getenv("HOME") ) ) {
	perror("chdir failed: ");
      }
    }
    return 1;
  }
  return 0;
}

/* 
   Helper function to execute a given command in the foreground or
   background with a given argv.

   Inputs: int bg - 0 if command runs in foreground; 1 otherwise
           char** argv - argv of command to execute, assumed with no
	                 trailing & or pipe. Full path to executable
			 is assumed present.

   Returns: 0 if execution was successful
*/
int execCommand( int bg, char ** argv ) {

  int pid;
  if ( ( pid = execChild( argv, 0, 0, 0, NULL )) < 0 ) {
    return -1;
  }
  
  // Parent optionally waits for child to finish
  if ( ! bg ) {  
    int status;
    if (waitpid(pid, &status, 0) < 0 ) {
      // May be triggered if SIGCHLD handler runs before
      // this line does. This is nondeterministic at runtime.
      if ( errno != ECHILD ) {
	perror("waitpid");
      }
    }
  }
  
  return 0;
}

/* 
   Handler for SIGCHLD. Reaps as many children as possible, but
   does not wait for any children to finish. Prints the exit
   status of each child as it exits.

   Inputs: int sig - signal number
   Returns: None
*/
void sigchld(int sig)
{
  int retpid, status;
  while((retpid = waitpid(-1, &status, WNOHANG))>0){
    if(WIFEXITED(status))
      fprintf(stderr, 
	      "Child %d terminated normally with exit status %d\n", 
	      retpid, WEXITSTATUS(status));
    else
      fprintf(stderr, 
	      "Child %d terminated abnormally with exit status %d\n", 
	      retpid, WEXITSTATUS(status));
  }

  fflush(stderr);
}
